package demo.java.sql;

import java.sql.Connection;
import java.sql.SQLException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 锁就是防止其他事务访问指定的资源的手段。锁是实现并发控制的主要方法，是多个用户能够同时操纵同一个数据库中的数据而不发生数据不一致现象的重要保障。
 * 一般来说，锁可以防止脏读、不可重复读和幻觉读。
 * <p>
 * 事务并发产生的问题：脏读、不可重复读、幻读。
 * <li>1 脏读：修改时加排他锁，直到事务提交后才释放，读取时加共享锁，读取完释放事务1读取数据时加上共享锁后（这
 * 样在事务1读取数据的过程中，其他事务就不会修改该数据），不允许任何事物操作该数据，只能读取，之后1如果有更新操作，那么会转换为排他锁，其他事务更
 * 无权参与进来读写，这样就防止了脏读问题。但是当事务1读取数据过程中，有可能其他事务也读取了该数据，读取完毕后共享锁释放，此时事务1修改数据，修改
 * 完毕提交事务，其他事务再次读取数据时候发现数据不一致，就会出现不可重复读问题，所以这样不能够避免不可重复读问题。
 * <li>2 不可重复读：读取数据时加共享锁，写数据时加排他锁，都是事务提交才释放锁。
 * 读取时候不允许其他事物修改该数据，不管数据在事务过程中读取多少次，数据都是一致的，避免了不可重复读问题      
 * <li>3 幻读问题：采用的是范围锁RangeS RangeS_S模式，锁定检索范围为只读，这样就避免了幻影读问题，在这里有个描述范围锁的文章
 * 
 *
 */
public class ConnectionDemo {

    private static Logger logger = LoggerFactory.getLogger(ConnectionDemo.class);

    /**
     * 在事务进行过程中，未结束之前，DML语句是不会更改底层数据，只是将历史操作记录一下，在内存中完成记录。
     * 只有在事务结束的时候，而且是成功的结束的时候，才会修改底层硬盘文件中的数据
     * <p>
     * 事务的四大特性（ACID）:
     * <ol>
     * <li>原子性（Atomicity）：事务是最小单位，不可再分。
     * <li>一致性（Consistency）:事务要求所有的DML语句操作的时候，必须保证同时成功或者同时失败。
     * <li>隔离性（Isolation） :事务A和事务B之间具有隔离性。
     * <li>持久性（Durability） ：是事务的保证，事务终结的标志(内存的数据持久到硬盘文件中)。
     * </ol>
     * 如果不考虑事务的隔离性，会发生的几种问题：
     * <ul>
     * <li>脏读: 脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
     * <li>不可重复读: 不可重复读是指在对于数据库中的某个数据，一个事务范围内多次查询却返回了不同的数据值，这是由于在查询间隔，被另一个事务修改并提交了。
     * <li>虚读(幻读):幻读是事务非独立执行时发生的一种现象。
     * 例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作，这时事务T2又对这个表中插入了一行数据项，而这个数据项的数值还是为“1”并且提交给数据库。
     * 而操作事务T1的用户如果再查看刚刚修改的数据，会发现还有一行没有修改，其实这行是从事务T2中添加的，就好像产生幻觉一样，这就是发生了幻读。
     * </ul>
     * 在MySQL数据库中默认的隔离级别为Repeatable read(可重复读)。 而在Oracle数据库中，只支持Serializable
     * (串行化)级别和Read committed (读已提交)这两种级别，其中默认的为Read committed级别。
     * <p>
     * <li>事务开启标志： 任何一条DML语句(insert、update、delete)执行，标志事务的开启
     * <li>事务结束标志(提交或者回滚)： - 提交：成功的结束，将所有的DML语句操作历史记录和底层硬盘数据来一次同步 -
     * 回滚：失败的结束，将所有的DML语句操作历史记录全部清空
     */
    public static void printTransactionIsolation(Connection connection) throws SQLException {
        // 获取事务隔离级别
        int transactionIsolation = connection.getTransactionIsolation();
        /*
         * 以下四种隔离级别最高的是Serializable级别，最低的是Read uncommitted级别，当然级别越高，执行效率就越低。
         * 像Serializable这样的级别，就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待，
         * 所以平时选用何种隔离级别应该根据实际情况。
         */
        switch (transactionIsolation) {
        // 不支持事务
        case Connection.TRANSACTION_NONE:
            logger.info("事务隔离界别：TRANSACTION_NONE");
            break;
        /**
         * (读未提交)最低级别，任何情况都无法保证。
         * <li>事务A和事务B，事务A未提交的数据，事务B可以读取到
         * <li>- 这里读取到的数据叫做“脏数据”。
         * <li>- 这种隔离级别最低，这种级别一般是在理论上存在，数据库隔离级别一般都高于该级别
         */
        case Connection.TRANSACTION_READ_UNCOMMITTED:
            logger.info("事务隔离界别：TRANSACTION_READ_UNCOMMITTED");
            break;
        /**
         * (读已提交)：可避免脏读的发生。
         * <li>事务A和事务B，事务A提交的数据，事务B才能读取到
         * <li>- 这种隔离级别高于读未提交
         * <li>- 换句话说，对方事务提交之后的数据，我当前事务才能读取到
         * <li>- 这种级别可以避免“脏数据”
         * <li>- 这种隔离级别会导致“不可重复读取”
         * <li>- Oracle默认隔离级别
         */
        case Connection.TRANSACTION_READ_COMMITTED:
            logger.info("事务隔离界别：TRANSACTION_READ_COMMITTED");
            break;
        /**
         * (可重复读)：可避免脏读、不可重复读的发生。
         * <li>- 事务A和事务B，事务A提交之后的数据，事务B读取不到
         * <li>- 事务B是可重复读取数据
         * <li>- 这种隔离级别高于读已提交
         * <li>- 换句话说，对方提交之后的数据，我还是读取不到
         * <li>- 这种隔离级别可以避免“不可重复读取”，达到可重复读取
         * <li>- 比如1点和2点读到数据是同一个
         * <li>- MySQL默认级别
         * <li>- 虽然可以达到可重复读取，但是会导致“幻像读”
         */
        case Connection.TRANSACTION_REPEATABLE_READ:
            logger.info("事务隔离界别：TRANSACTION_REPEATABLE_READ");
            break;
        /**
         * (串行化)：可避免脏读、不可重复读、幻读的发生。
         * <li>- 事务A和事务B，事务A在操作数据库时，事务B只能排队等待
         * <li>- 这种隔离级别很少使用，吞吐量太低，用户体验差
         * <li>- 这种级别可以避免“幻像读”，每一次读取的都是数据库中真实存在数据，事务A与事务B串行，而不并发
         */
        case Connection.TRANSACTION_SERIALIZABLE:
            logger.info("事务隔离界别：TRANSACTION_SERIALIZABLE");
            break;

        default:
            logger.info("未知的事务隔离界别");
            break;
        }

    }

    public static void testConnection(Connection connection) throws SQLException {
        // 打印事务隔离级别
        printTransactionIsolation(connection);
    }
}
